import pandas as pd
import backtrader as bt
import datetime

import utils.MyTT as Mytt
# from backtrader.analyzers import SharpeRatio, TradeAnalyzer
from backtrader.analyzers import (
    SharpeRatio, 
    AnnualReturn,
    DrawDown,
    TradeAnalyzer,
    Returns,
    TimeReturn
)
import os

def testXinhao(df):
    df['rsi25'] = Mytt.RSI(df['close'],25)
    df['rsi150'] = Mytt.RSI(df['close'],150)
    df['ema120'] = Mytt.EMA(df['close'],120)
    df['cclose'] = df['close'].shift(1)

    df['cross_rsi'] = Mytt.CROSS(df['rsi25'],df['rsi150'])

    df['signal'] = 0
    df.loc[(df['close'].shift(1)  < df['ema120']) & (df['cross_rsi'] ==1) ,'signal'] = 1 # 做多信号距离越远 信号可信度越高
    df.loc[(df['close'].shift(1)  > df['ema120']) & (df['cross_rsi'] == -1) ,'signal'] = -1 # 做多信号距离越远 信号可信度越高
    return df

class FixedCommission(bt.CommInfoBase):
    params = (
        ('commission', 0.0005),  # 0.05%
        ('stocklike', True),     # 股票模式（size=股数）
        ('mult', 1.0),           # 合约乘数（股票=1.0）
        ('margin', 0.01),         # 保证金模式（即 1% 现金交易，可使用100倍杠杆）
    )

    def _getcommission(self, size, price, pseudoexec):
        # 手动计算佣金（确保 size 和 price 正确）
        commission = abs(size) * price * self.p.commission
        return commission
    

class BacktestEngine:
    """一键回测引擎"""
    
    def __init__(self, strategy_class, data_path,fromdate=None,todate=None,cycle='15m', **kwargs):
        self.cerebro = bt.Cerebro()
        self.cerebro.addstrategy(strategy_class)
        self.number = self.load_data(data_path,fromdate,todate,cycle)

        self.add_analyzers()
    
    # 智能读取数据
    def loadCsvData(self,data_path,cycle = '15m',limit=0):
        # 读取加载数据
        # instId  要读取的币种
        # cycle   要读取的窗口周期
        # limit   要读取的记录条数（由新到旧）  0 为返回全部
        filePath =data_path
        if not os.path.exists(filePath):
            print(f"文件路径不存在: {filePath}")
            df = pd.DataFrame()
        
        # 读取 CSV 文件，不预先设置索引
        df = pd.read_csv(filePath)
        if len(df)==0:
            df = pd.DataFrame()
        else:
            # 转换时间戳 (毫秒 -> datetime)
            df['timestamp'] = pd.to_datetime(df['timestamp']+8*3600*1000, unit='ms')
            
            # 将时间戳设置为索引
            df.set_index('timestamp', inplace=True)
            if cycle!='15m':
                        # 统一将周期转换为 Pandas 支持的 resample 规则
                resample_rule = {
                    '1h': '1h',    # 1小时
                    '4h': '4h',     # 4小时
                    '1d': '1D',     # 1天（自然日）
                    # '1w': '1W',     # 1周
                    # '1m': '1M'      # 1月
                }.get(cycle.lower(), cycle)  # 默认直接使用输入的 cycle
                # 周期不一致 则聚合周期
                        # 转换为指定的时间级别
                # 使用 resample 进行聚合
                df_resampled = df.resample(resample_rule).agg({
                    'open': 'first',
                    'high': 'max',
                    'low': 'min',
                    'close': 'last',
                    'volume': 'sum'
                })
                is_incomplete = False
                    # 检查是否需要丢弃未完成的 K 线
                if cycle.endswith(('h', 'd', 'w', 'm')):  # 如果是小时/天/周/月级别
                    now = pd.Timestamp.now(tz=df.index.tz) if df.index.tz else pd.Timestamp.now()
                    last_kline_time = df_resampled.index[-1]

                    # 计算当前周期是否应该结束
                    if cycle == '1d':
                        # 对于自然日，判断今天是否已经走完
                        today_start = pd.Timestamp(now.date())  # 今天的 00:00:00
                        is_incomplete = (last_kline_time == today_start) and (now < today_start + pd.Timedelta(days=1))
                    elif cycle == '4h':
                        # 对于4小时，判断当前是否在最后一个4小时周期内
                        next_kline_time = last_kline_time + pd.Timedelta(hours=4)
                        is_incomplete = now < next_kline_time
                    # ...（其他周期类似）...
                    elif cycle == '1h':
                        next_kline_time = last_kline_time + pd.Timedelta(hours=1)
                        is_incomplete = now < next_kline_time


                    if is_incomplete:
                        df = df_resampled.iloc[:-1]  # 丢弃未完成的 K 线
                    else:
                        df = df_resampled
        if limit == 0:
            return df
        else:
            return df.tail(limit)

    def load_data(self, data_path,fromdate=None,todate=None,cycle='15m'):
        if not os.path.exists(data_path):
            raise FileNotFoundError(f"文件路径不存在: {data_path}")
        df = self.loadCsvData(data_path,cycle)
        if fromdate!=None and todate !=None:
            data = bt.feeds.PandasData(dataname=df, fromdate=fromdate, todate=todate)
        else:
            data = bt.feeds.PandasData(dataname=df)
        
        assert not df.isnull().any().any(), "数据存在缺失值！"
        self.cerebro.adddata(data)
        return len(df)

    def add_analyzers(self):
        """添加专业分析器集合"""
        # 计算夏普比率 
        self.cerebro.addanalyzer(SharpeRatio, _name='sharpe', riskfreerate=0.0)
        # 计算最大回撤（Maximum Drawdown），表示从峰值到谷底的最大损失
        self.cerebro.addanalyzer(DrawDown, _name='drawdown')
        # 提供详细的交易统计信息，包括交易数量、胜率、平均盈亏等。  
        # total: 总交易数。 won: 获胜交易数。 lost: 亏损交易数。 pnl: 总利润和损失。 average: 平均利润和损失。winrate: 胜率。
        self.cerebro.addanalyzer(TradeAnalyzer, _name='trades')
        # 计算策略的累积收益率。  
        #   - ``rtot``: 总收益率
        #   - ``ravg``: 平均收益率
        #   - ``rnorm``: 年化收益率
        #   - ``rnorm100``: 年化百分比收益率
        self.cerebro.addanalyzer(Returns, _name='returns')


    def run(self, initial_cash=100000):
        if self.number == 0:
            return None,None
        self.cerebro.broker.setcash(initial_cash)
        # 手续费 万5
        # self.cerebro.broker.setcommission(commission=0.0005,margin = 0.5,percabs=True )
        # 使用自定义的佣金计算规则
        self.cerebro.broker.addcommissioninfo(FixedCommission())
        results = self.cerebro.run()
        return self._parse_results(results[0], initial_cash),self.cerebro
    

    # 返回汇总数据
    def _parse_results(self, strategy, initial_cash):
        """解析所有分析器结果"""
        # 打印数据长度和时间范围
        # print(f"数据条数: {len(strategy.data)}")
        # print(f"时间范围: {self.min_data} 到 {self.max_data}")
    
        # 基础指标
        final_value = strategy.broker.getvalue()
        pnl = final_value - initial_cash
 
    
        # 获取分析器数据
        sharpe = strategy.analyzers.sharpe.get_analysis()
        drawdown = strategy.analyzers.drawdown.get_analysis()
        trades = strategy.analyzers.trades.get_analysis()
        returns = strategy.analyzers.returns.get_analysis()
        # print(returns)
        # exit()
        # 获取k线数据
        returns_data = self.getCloseData(strategy)
        yearListRatio = self.getYearListRatio(strategy)

        # 交易统计 默认数据
        trades_data = {
            'total': 0,# 总交易次数
            'won': 0, # 盈利次数
            'lost': 0, # 亏损次数
            'streak_won':0, # 连胜 连败场次
            'streak_lost':0, # 连胜 连败场次
            'pnl': 0, # 总利润和损失。
            "long_ratio": '',# 多空开单比
            "long_pnl":0,# 多头盈利
            "short_pnl":0,# 空头盈利
            'average':0, # 平均利润和损失。winrate
            'winrate':0, # 胜率。
        }
        # total: 总交易数。 won: 获胜交易数。 lost: 亏损交易数。 pnl: 总利润和损失。 average: 平均利润和损失。winrate: 胜率。
        tradesTotal = trades.total.total
        if tradesTotal>3:
            if trades.long.total>0 and trades.short.total:
                long_ratio = trades.long.total/trades.short.total
            elif trades.long.total>0:
                long_ratio =1
            else:
                long_ratio =-1
            trades_data = {
                'total': tradesTotal,# 总交易次数
                'won': trades.won.total, # 盈利次数
                'lost': trades.lost.total, # 亏损次数
                'streak_won':f"{trades.streak.won.longest}", # 连胜 连败场次
                'streak_lost':f"{trades.streak.lost.longest}", # 连胜 连败场次
                'pnl': trades.pnl.gross.total, # 总利润和损失。
                "long_ratio": long_ratio,# 多空开单比
                "long_pnl":trades.long.pnl.total,# 多头盈利
                "short_pnl":trades.short.pnl.total,# 空头盈利
                'average':trades.pnl.gross.average, # 平均利润和损失。winrate
                'winrate':trades.won.total / tradesTotal, # 胜率。
            }
        sharperatio = sharpe.get('sharperatio', 0.0)
        # 累计收益率
        total_return = (final_value-initial_cash)/initial_cash
        # 年化收益率
        year_num =  (strategy.equity_curve[-1]['date'] - strategy.equity_curve[0]['date']).days 
        annualized_return = (final_value / initial_cash) ** (1 / ( year_num/ 365.25)) - 1
        return {
            'meta': {
                'period': f"{strategy.equity_curve[0]['date']} 至 {strategy.equity_curve[-1]['date']}",
                'days': year_num,
                'years': year_num/ 365.25
            },
            'performance': {
                'final_value': final_value, # 期末资产
                'net_pnl': pnl, # 净收益
                'total_return':total_return, # 累计收益率
                'ravg':returns.get('ravg',0), # 年平均收益率
                'annualized_return': annualized_return, #年化收益率
                'sharpe_ratio': sharperatio if sharperatio!=None else 0 , #夏普比率
                'max_drawdown': drawdown.get('max', {}).get('drawdown', 0.0), # 最大回撤
                'max_drawdown_period': drawdown.get('max', {}).get('len', 0),# 持续天数
                "dapan_ratio":(strategy.equity_curve[-1]['close'] - strategy.equity_curve[0]['close']) / strategy.equity_curve[0]['close'],# 计算大盘涨幅
            },
            'trades': trades_data,
            'returns': returns_data,
            "yearListRatio":yearListRatio,
        }

    # 解析图表数据k线数据 和 资产图表数据
    def getCloseData(self,strategy):
        result = {
            'dates':[],
            'values':[],
            'closes':[],
        }
        for item in strategy.equity_curve:
            result['dates'].append(item['date'].strftime('%Y-%m-%d %H:%M'))
            result['values'].append(item['value'])
            result['closes'].append(item['close'])

        result['values'] = self.net_worth(result['values'])
        # result['closes'] = self.net_worth(result['closes'])
        return result
    
    # 获取每年的收益
    def getYearListRatio(self,strategy):
        # 初始化一个字典来按年份存储数据
        yearly_data = {}
        data = strategy.equity_curve
        # 遍历数据并按年份分组
        for item in data:
            year = item['date'].strftime('%Y')
        
            if year not in yearly_data:
                yearly_data[year] = {
                    'values': [],
                    'closes': []
                }
        
            yearly_data[year]['values'].append(item['value'])
            yearly_data[year]['closes'].append(item['close'])
        
        # 计算每年的跌涨幅度
        result = []
        for year, items in yearly_data.items():
            values = items['values']
            closes = items['closes']
        
            # 计算 value 的跌涨幅度
            value_start = values[0]
            value_end = values[-1]
            value_change = (value_end - value_start) / value_start * 100
        
            # 计算 close 的跌涨幅度
            close_start = closes[0]
            close_end = closes[-1]
            close_change = (close_end - close_start) / close_start * 100
        
            # 格式化结果
            result.append({
                'date': f"{year}年",
                'value': f"{value_change:.2f}%",
                'close': f"{close_change:.2f}%"
            })
        return result

        # 归一化函数
    def normalize(self,data):
        min_val = min(data)
        max_val = max(data)
        return [(x - min_val) / (max_val - min_val) for x in data]
    
    # 净值函数
    def net_worth(self,data):
        initial_value = data[0]
        return [x / initial_value for x in data]